package baseClass;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
/**
 * 
 * @author Carlos Victor
 *
 */
public class Recomendacoes {
	
	Map<Integer,TreeSet<String>> restaurantes = new TreeMap<Integer,TreeSet<String>>();
	List<String> nomesRestaurantes = new ArrayList<String>() ;
	List<String> linhas;
	List<String> enderecos;
	List<User> usuarios;
	String caminhoArquivo1,caminhoArquivo2;
	
	/**
	 * Cria um sistema de recomendacoes a partir de dois caminhos de arquivos contendo informacoes sobre opnioes e enderecos dos estabelecimentos.
	 * @param opnioes
	 * 		As opnioes dos usuarios referente a cada estabelecimento.
	 * @param endereco
	 * 		Os enderecos dos estabelecimentos.
	 * @throws Exception
	 * 		Se uma das listas for vazia.
	 */
	public Recomendacoes(String opnioes,String endereco)throws Exception{
		if (opnioes == null || opnioes.isEmpty()) {
			throw new Exception("arquivo de opnioes nao pode ser nulo ou vazio.");
		}
		
		if ( endereco == null || endereco.isEmpty()) {
			throw new Exception("arquivo de enderecos nao pode ser nulo ou vazio.");
		}
		
		Arquivo op = new Arquivo();
		op.lerArquivo(opnioes);
		Arquivo end = new Arquivo();
		end.lerArquivo(endereco);
		
		caminhoArquivo1 = opnioes;
		caminhoArquivo2 = endereco;
		this.linhas = op.getLinhasDoArquivoLido();
		this.enderecos = end.getLinhasDoArquivoLido();
		usuarios = new ArrayList<User>();
		adicionaRestaurantes();
		carregarUsuarios();
	}
	
	/**
	 * Adiciona os restauresnte para que seja iniciado o sistema.
	 */
	public void adicionaRestaurantes(){
		List<Integer> Notas = new ArrayList<Integer>();
		
		String[] linhaZero = linhas.get(0).split(";");
		
		for (int i = 2; i < linhaZero.length; i++) {
			nomesRestaurantes.add(linhaZero[i]);
		}
		
		for (int j = 2; j < nomesRestaurantes.size()+2; j++) {
			int soma = 0;
			for (int i = 1; i < this.linhas.size(); i++) {
				String[] classificacao = linhas.get(i).split(";");
				String[] avaliacao = classificacao[j].split(":");
				soma += Integer.parseInt(avaliacao[0].trim());
			}
					
			
			Notas.add(soma);
		}
		
		for (int i = 0; i < nomesRestaurantes.size(); i++) {
			if(restaurantes.containsKey(Notas.get(i))){
				restaurantes.get(Notas.get(i)).add(nomesRestaurantes.get(i));
			}else{
				TreeSet<String> bares = new TreeSet<String>();
				bares.add(nomesRestaurantes.get(i));
				restaurantes.put(Notas.get(i),bares);
			}
			
		}
	}
	
	/**
	 * Carrega o perfil de cada usuario.
	 */
	public void carregarUsuarios(){
		int indice = 0;
		for (int i = 1; i < this.linhas.size(); i++) {
			String[] infos = this.linhas.get(i).split(";");
			Map<String,Integer> opnioes = new HashMap<String,Integer>(); 
			for (int j = 2; j < infos.length; j++) {
				String[] avaliacao = infos[j].split(":");
				String notaInt = avaliacao[0].trim();
				opnioes.put(nomesRestaurantes.get(j-2),Integer.parseInt(notaInt) );
			}
			String nome = infos[1];
			if (nome.isEmpty()) {
				nome = "Anonimo" + indice;
				indice ++;
			}
			try {
				usuarios.add(new User(nome,opnioes));
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
		}
	}
	
	/**
	 * Retorna o caminho do primeiro arquivo
	 * @return
	 * 		O caminho do primeiro arquivo em forma de string.
	 */
	public String getCaminhoArquivo1() {
		return caminhoArquivo1;
	}
	
	/**
	 * Retorna o caminho do segundo arquivo
	 * 
	 * @return
	 * 		O caminho do segundo arquivo em forma de string.
	 */
	public String getCaminhoArquivo2() {
		return caminhoArquivo2;
	}
	
	
	/**
	 * Retorna uma lista de objetos User.
	 * 
	 * @return
	 * 		A lista de objetos User.
	 */
	public List<User> getUsuarios(){
		return usuarios;
	}
        
        /**
         * Adiciona um usuario a lista de usuarios existentes
         * @param novoUser
         *      novo usuario a ser adicionado na lista existente
         */
        public void adicionaUser(User novoUser){
            usuarios.add(novoUser);
        }
	

	/**
	 *Retorna uma lista contento nome,endereco e especialidade de cada restaurante.
	 * @return
	 */
	public List<String> getEnderecos() {
		return this.enderecos;
	}
	
	/**
	 * Retorna uma lista contendo as opinioes de cada usuario referente aos restauranes.
	 * @return
	 * 		A lista de opinioes dos usuarios referente aos restaurantes.
	 */
	public List<String> getOpnioes() {
		return linhas;
	}
	
	public List<String> ordenacaoPorRefeicao(){
		List<String> tipoPrato = new ArrayList<String>();
		for (String end : enderecos) {
			String[] infos = end.split(";");
			tipoPrato.add(infos[2] + " | " + infos[0]);
		}
		Collections.sort(tipoPrato);
		
		return tipoPrato;
	}
	/**
	 * Ordena os restaurantes por ordem alfabetica.
	 * @return
	 * 		A lista de restaurantes ordenados por ordem alfabetica.
	 */
	public List<String> ordenacaoAlfabetica(){
		List<String> restaurantesOrdenado = nomesRestaurantes;
		Collections.sort(restaurantesOrdenado);
		return restaurantesOrdenado;
	}
	/**
	 * Retorna uma String contento os R melhores restaurantes.
	 * 
	 * @param R
	 * 		Numero de restaurantes a ser retornado.		
	 * @return
	 * 		Uma String contento os R melhores restaurantes.
	 * @throws Exception
	 * 		Se o numero de restaurantes a ser retornado for menor que 1 ou maior que o numero total de restaurantes.
	 */
	public List<String> popularidadeGlobal(int R)throws Exception{
		if (R < 1) {
			throw new Exception("Deve ser passado no minimo 1 informacao");
		}
	
		List<String> ordenada = new ArrayList<String>();
		for (TreeSet<String> lista : restaurantes.values()) {
			for (String restaurante : lista) {
				ordenada.add(restaurante);
				
			}
		}
		
		Collections.reverse(ordenada);
		
		if (R > ordenada.size() ) {
			throw new Exception("O numero de opnioes nao pode ser maior que a quantidade de restaurantes");
		}
		
		return ordenada.subList(0, R);
	}
	
	/**
	 * Compara os algoritmos de recomendacoes personalizadas e recomendacoes global.
	 * @param user
	 * 		Usuario passado para a realizar a comparacao.
	 * @param numIntens
	 * 		Numero de itens a ser retornado
	 * @return
	 * 		A taxa de acerto dos algoritmos.
	 * @throws Exception 
	 * 		Se o numero de itens for maior que o numero de restaurantes.
	 */
	public String comparaAlgoritmos(int numItens) throws Exception{
		List<Double> percents = new ArrayList<Double>();
		List<Double> percentsGlobal = new ArrayList<Double>();
		
		Iterator<User> us = usuarios.iterator();
		
		while (us.hasNext()) {
			int pontosPersonalizados = 0;
			int pontosGlobal = 0;
			User proxUser = us.next();
			if (proxUser.getNumOpinioesPositivas() > 0) {
				for (int i = 0; i < proxUser.getNumOpinioesPositivas(); i++) {
					String zerado = proxUser.ZerarOpiniao();
					List<String> personalizado = null;
					List<String> global = null;
					
					personalizado = PopularidadePersonalizada(proxUser, numItens);
					
					global = popularidadeGlobal(numItens);
					
					if (!(zerado.equals("Opinioes zeradas"))) {
						if (personalizado.contains(zerado)) {
							pontosPersonalizados += 1;
						}
						if (global.contains(zerado)) {
							pontosGlobal += 1;
						}
					}
				}
				percents.add(((pontosPersonalizados*100/(double) (proxUser.getOpnioesZeradas().keySet().size()))));
				percentsGlobal.add((pontosGlobal*100)/(double) (proxUser.getOpnioesZeradas().keySet().size()));
				
				try {
					proxUser.recuperarOpinioes();
				} catch (Exception e) {
					e.printStackTrace();
				}

			}
						
		}	
		
		double somaPersonalizado = 0;
		for (Double double1 : percents) {
			somaPersonalizado += double1;
		}
		double somaGlobal = 0;
		for (Double double1 : percentsGlobal) {
			somaGlobal += double1;
		}
		
		return somaPersonalizado/percents.size() + "\n" + somaGlobal/percentsGlobal.size();
	}

	/**
	 * Retorna uma String contendo todos os restaurantes disponiveis,organizando de acordo com a popularidade de cada um.
	 * @return
	 * 		Uma string contendo os restaurantes,organizando de acordo com a popularidade.Do maior para o meno.
	 */
	public List<String> getRestaurantes(){
		List<String> ordenada = new ArrayList<String>();
		for (TreeSet<String> lista : restaurantes.values()) {
			for (String restaurante : lista) {
				ordenada.add(restaurante);
				
			}
		}
		Collections.reverse(ordenada);
		
		return ordenada;
	}
	
	/**
	 * Compara as opnioes de varios usuarios e retorna aqueles com maior semelhan�a de gosto.
	 * 
	 * @param usuario
	 * 		O perfil do usuario a ser comparado com os demais.
	 * @param numOpnioes
	 * 		A quantidade de opnioes a ser retornada.
	 * @return
	 * 		Uma String contendo as opnioes mais semelhantes.
	 * @throws Exception
	 * 		Se o numero de opinioes for menor que 1 ou se for maior que o numero total de usuarios.
	 */
	public List<String> PopularidadePersonalizada(User usuario,int numOpnioes)throws Exception{
		if (usuario == null) {
			throw new Exception("Usuario nao pode ser nulo");
		}
		if (numOpnioes < 1) {
			throw new Exception("Numero de opnioes invalido");
		}
		if (numOpnioes > nomesRestaurantes.size()) {
			throw new Exception("Numero de opinioes nao pode ser maior que o numero de estabelecimentos.");
		}	
		
		List<String> estabelecimentos = new ArrayList<String>();
		
		for (User user : usuario.getSemelhantes(usuarios)) {
			List<Integer> notas = new ArrayList<Integer>();
			
			for (Integer nota : user.getMelhores().keySet()) {
				if (!(notas.contains(nota))) {
					notas.add(nota);
				}
			}
			Collections.sort(notas);
			Collections.reverse(notas);
			
			for (Integer integer : notas) {
				for (String restaurante : user.getMelhores().get(integer)) {
					
					if ((!(estabelecimentos.contains(restaurante))) && usuario.getOpinioes().get(restaurante) == 0) {
						estabelecimentos.add(restaurante);
					}
				}
				
			}
		}
		if (estabelecimentos.size() < numOpnioes) {
			numOpnioes = estabelecimentos.size();
		}
		return estabelecimentos.subList(0, numOpnioes);
	}
	public String getOpinioesDeSemelhante(User user){
		
		String avaliacoes = "";
		for (Integer nota : user.getMelhores().keySet()) {
			for (String estabelecimento : user.getMelhores().get(nota)) {
				avaliacoes += "Estabelecimento: "+ estabelecimento + "\nNota: " + nota + "\n__________________________\n";
			}
		}
		
		return avaliacoes;
	}
	
}
	